# forward-compat boilerplate
from __future__ import absolute_import
from __future__ import with_statement
__metaclass__ = type

import thread
import fenton


# descriptor decorator
class resource:
    def __init__(self, factory):
        self.factory = factory
        self.id = getattr(factory, '__name__', str(factory))
    def __get__(self, obj, Class):
        if obj is None:
            return self.factory
        return obj.get_resource(self.id, self.factory)



class toggle:

    def __init__(self, f):
        import threading
        self.value = None
        self.lock = threading.Lock()
        self.f = f

    def __get__(self, obj, Class):
        return self.value

    def __set__(self, obj, value):
        orig = self.value
        with self.lock:
            self.value = value
        if orig is not None and orig is not value:
            self.f(obj, value)



class ThreadCheckError(Exception):
    pass




class ThreadChecker:
    def __init__(self, name, throw):
        self.name = name
        self.owner = thread.get_ident()
        self.throw = throw

    def __bool__(self):
        if thread.get_ident() == self.owner:
            return True
        if self.throw:
            raise ThreadCheckError(self)
        return False

    def __repr__(self):
        from fenton import util
        x = util.uint(self.owner)
        y = util.uint(thread.get_ident())
        return '<ThreadChecker: %s owner=%x this=%x>' % (self.name, x, y)




class BaseContext:

    app = property(lambda x:x.request.app)
    user = property(lambda x:x.request.user)
    tz = property(lambda x:x.request.tz)
    config = property(lambda x:x.request.app.config)

    def __init__(self, request=None):
        from fenton import util
        self.screen = None
        self.request = request
        self.id = util.uniqid()
        self._resources = {}

    def __getstate__(self):
        d = self.__dict__.copy()
        d['request'] = None
        d['_resources'] = {}
        return d

    def __repr__(self):
        return '%s<%s>' % (self.__class__.__name__, self.id)

    # context manager for 'with' statement
    def __enter__(self):
        self.tx_start()
        return self

    def __exit__(self, *errors):
        if any(errors):
            self.tx_rollback()
        else:
            try:
                self._flush_deleted()
                self.tx_finish()
                self.tx_commit()
            except:
                self.tx_rollback()
                raise

    def get(self, __id, *args, **kw):
        meta = obj = None
        if isinstance(__id, basestring):
            meta, arg = self.app.get(__id)
            if arg:
                obj = meta.reconstruct(arg, self)
        elif isinstance(__id, type):
            meta = fenton.getmeta(__id)
            if args:
                obj = meta.load_model(self, args)
        else:
            raise NotImplementedError
        if obj is None:
            if args:
                kw.update(dict((meta.key[i], arg)
                               for (i, arg) in enumerate(args)))
            obj = meta.construct_model(self, **kw)
        elif kw:
            fenton.getmeta(obj).update_model(self, obj, kw)
        return obj

    __deleted = None
    def delete(self, obj):
        if self.__deleted is None:
            self.__deleted = []
        self.__deleted.append(obj)

    def _flush_deleted(self):
        if self.__deleted:
            for obj in self.__deleted:
                fenton.getmeta(obj).delete_model(self, obj)
            self.__deleted = None

    def update(self, obj, **attrs):
        return fenton.getmeta(obj).update_model(self, obj, attrs)

    def get_view(self, obj):
        return fenton.getmeta(obj).get_view(self, obj)

    def close(self):
        self.close_resources()

    def thaw(self, request):
        orig = self.request
        if orig is not request:
            self.request = request
            if orig:
                request.update(orig)
            if self.screen:
                self.screen.thaw()

    def get_resource(self, id, factory):
        from fenton import logging
        rs = self._resources.get(id)
        if rs is None:
            rs = factory(self)
            self._resources[id] = rs
            throw = not self.app.config.get('allow_resource_sharing', False)
            rs.__thread_check = ThreadChecker(id, throw)
        elif not rs.__thread_check:
            logging.log.warn('THREAD CHECK FAILED: %s' % rs.__thread_check)
        return rs

    def close_resources(self):
        from fenton import logging
        for id in self._resources.keys():
            rs = self._resources.pop(id)
            del rs.__thread_check
            close = getattr(rs, 'close', None)
            close and close()

    # template methods for context manager

    def tx_start(self):
        pass

    def tx_finish(self):
        return True

    def tx_commit(self):
        for rs in self._resources.values():
            if hasattr(rs, 'commit'):
                rs.commit()

    def tx_rollback(self):
        for rs in self._resources.values():
            if hasattr(rs, 'rollback'):
                rs.rollback()




class BaseApp:
    context_class = BaseContext

    def __init__(self, container, config=None):
        self.container = container
        self.threadpool = container.get_threadpool()
        self.config = config = config or {}
        self.debug = bool(config.get('debug'))
        self.configure()
        from fenton import security
        security.initialize(self)
        from fenton import model
        model.compile_all()

    def configure(self):
        pass

    def get(self, id):
        import fenton.model
        return fenton.model.REGISTRY.get(id)

    def source_dirs(self):
        import os, sys
        mod = sys.modules[self.__class__.__module__]
        return [os.path.dirname(mod.__file__),
                os.path.dirname(__file__)]

    def create_context(self, request):
        return self.context_class(request)

    def get_login_url(self, quoted_url):
        return None

    def call_async(self, f):
        if not self.threadpool:
            return f()
        def work(f=f, name=str(f), tracker=self.threadpool.worker_tracker):
            tracker[thread.get_ident()][1] = name
            f()
        self.threadpool.add_task(work)




class Shell:
    config = property(lambda x:x.app.config)
    debug  = property(lambda x:x.app.debug)
    def __init__(self, app):
        self.app = app




class Request:
    user_key = '~'
    model_key = '_'
    bind_key = '__'
    history_key = '^'
    context_key = '$'
    context_prefix = '_'
    history = None
    max_history = 32

    def new_context(self):
        return self.app.create_context(self)

    def close(self):
        pass

    def authenticate(self, user):
        user.authenticate(self)
        self.user = user

    def unauthenticate(self, user):
        user.unauthenticate(self)
        self.user = None

    def add_history(self, screen):
        history = self.get_history()
        if history is None:
            return
        latest = history and history[-1]
        path = screen.get_path()
        if not path:
            return
        args = screen.get_args()
        if latest and latest[0] == path:
            history[-1] = (path, args)
        else:
            history.append((path, args))
        while len(history) > self.max_history:
            del history[0]

    def get_history(self):
        return self.history




